﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using FileHelpers;
using SharpStockLib.Data;
using SharpStockLib.DataAccess.CSVMappers;

namespace SharpStockLib.DataAccess.Providers
{
    /// <summary>
    /// Adapter to get historical quotes from CSV files.
    /// </summary>
    public class CSVProvider : IDataProvider
    {
        private readonly string _csvPath;
        private readonly CSVType _type;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="csvPath">Path to the CSV file.</param>
        /// <param name="type">Type of the CSV file to parse. Currently, only YahooHisto and TradingBloxHisto are handled.</param>
        public CSVProvider(string csvPath, CSVType type)
        {
            _csvPath = csvPath;
            _type = type;
        }

        #region IDataProvider Members

        /// <summary>
        /// Gets the historical data for the instrument given in input from a CSV source. The CSV file will be looked for at the path specified in the constructor.
        /// Throws an IOException if the file could not be found or read (permissions, concurrent access, etc...).
        /// </summary>
        /// <exception cref="IOException">if the file could not be found or read (permissions, concurrent access, etc...)</exception>
        /// <param name="symbol">The name of the stock whose data must be retrieved. This is a dummy parameter, as all info from the file will be read anyway.</param>
        /// <param name="startDate">The date from which the data must be retrieved</param>
        /// <param name="endDate">The date until which the data must be retrieved</param>
        /// <returns>A List of IData matching the criteria.</returns>
        public IEnumerable<IData> GetHistoricalData(string symbol, DateTime? startDate, DateTime? endDate)
        {
            List<IData> lines = new List<IData>();
            FileHelperEngine engine;
            IMapper[] quotes;
            
            // Parses the data according to the type specified in the constructor.
            // TODO : error handling (format exceptions, etc...) and fallback on demand (try to parse using another type if explicitely asked by the caller)
            switch(_type)
            {
                case CSVType.YahooHisto:
                    engine = new FileHelperEngine(typeof(YahooStockHistorical));
                    quotes = (YahooStockHistorical[])engine.ReadFile(_csvPath);
                    lines.AddRange(quotes.Select(d => d.ToIData()).ToList());
                    break;

                case CSVType.TradingBloxHisto:
                    engine = new FileHelperEngine(typeof(TradingBloxFuturesHisto));
                    quotes = (TradingBloxFuturesHisto[])engine.ReadFile(_csvPath);
                    lines.AddRange(quotes.Select(d => d.ToIData()).ToList());
                    break;

                case CSVType.YahooInfo:
                    throw new NotImplementedException("Type YahooInfo not yet implemented.");
                case CSVType.GoogleHisto:
                    throw new NotImplementedException("Type GoogleHisto not yet implemented.");
                default:
                    throw new ArgumentOutOfRangeException();
            }

            // Applies a filter in order to avoid the dates we do not want. Ideally, this filter should be applied when reading the file to avoid reading useless data.
            return lines.Where(data => IsDateOK(data, startDate, endDate));
        }

        /// <summary>
        /// Rx helper for asynchronous reading. Not exactly rocket science. The point is to keep the compatiblity with Rx but this is definitely not the approrpiate way to consume this as there's no real asynchronism here.
        /// Moreover, if asynchronism was used, the consumer should be warned that order is not guaranteed. This is a problem when dealing with data ordered by date. This is not a reactive problem per-se.
        /// </summary>
        /// <exception cref="IOException">if the file could not be found or read (permissions, concurrent access, etc...)</exception>
        /// <param name="instrumentName">The name of the stock whose data must be retrieved. This is a dummy parameter, as all info from the file will be read anyway.</param>
        /// <param name="startDate">The date from which the data must be retrieved</param>
        /// <param name="endDate">The date until which the data must be retrieved</param>
        /// <returns>An IObservable feed of IData matching the criteria that the consumer will be able to use asynchronously.</returns>
        public IObservable<IData> GetHistoricalDataAsync(string instrumentName, DateTime? startDate, DateTime? endDate)
        {
            // Reactive Extensions : the List is transformed to an IObservable.
            return GetHistoricalData(instrumentName, startDate, endDate).ToObservable();
        }

        #endregion

        /// <summary>
        /// States whether the given data should be filtered or not. Basically, it works like this :
        /// - data is null : thanks but no thanks, returns false.
        /// - both start and end date set to null : means no filter, returns true.
        /// - end date but no start date : returns true if the date is not later than end date.
        /// - start date but no end date : returns true if the date is not sooner than start date.
        /// - both dates are not null : returns true if the data's date is included in the specified period.
        /// </summary>
        /// <param name="data">The data to check.</param>
        /// <param name="startDate">The beginning of the period the data's date should belong to.</param>
        /// <param name="endDate">The end of the period the data's date should belong to.</param>
        /// <returns>See method's description.</returns>
        private bool IsDateOK(IData data, DateTime? startDate, DateTime? endDate)
        {
            if (data == null)
                return false;

            // No filter on dates : returns true
            if (startDate == null && endDate == null)
                return true;

            DateTime inputDate = data.Date;
            
            if (startDate != null)
            {
                // Is the date OK regarding the start date ? If not, we can exit now
                bool startOK = inputDate >= startDate;
                if (!startOK)
                    return false;

                // No end date ? Then everything is fine.
                if (endDate == null)
                    return true;

                // Last check : is end date OK ?
                return inputDate <= endDate;
            }
            // No start date : only checks the end date
            return inputDate <= endDate;
        }
    }
}
